package chapter05._04_reflection;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;

import exercise.Test;

import java.lang.Class;

/**
* @ClassName:Reflection
* @Description:反射环节的学习
* @TODO:1. 分析类的字段、构造器、方法
* 		2. 
*/
public class Reflection {
	public static void main(String[] args) throws ReflectiveOperationException {	
		Scanner sc = new Scanner(System.in);
		String name = sc.next();
		Class cl = Class.forName(name);
		Class superCl = cl.getSuperclass();
		String modifiers = Modifier.toString(cl.getModifiers());
		
		if (modifiers.length() > 0) System.out.print(modifiers + " ");
		System.out.print("class " + name);
		if (superCl != null && superCl != Object.class)
			System.out.print(" extends " + superCl.getName());
		System.out.println(" {");
		
		Reflection.printFields(cl);
		Reflection.printConstructors(cl);
		Reflection.printMethods(cl);
		
		System.out.println("}");
	}
	
	public static void printFields(Class cl) {
		Field[] fields = cl.getDeclaredFields();
		
		for (var f : fields) {
			Class type = f.getType();
			String name = f.getName();
			String modifiers = Modifier.toString(f.getModifiers());
			System.out.print("    ");
			if (modifiers.length() > 0) System.out.print(modifiers + " ");
			System.out.println(type.getName() + " " + name + ";");
		}
	}
	
	public static void printConstructors(Class cl) {
		Constructor[] constructors = cl.getDeclaredConstructors();
		
		for (var cs : constructors) {
			String name = cs.getName();
			String modifiers = Modifier.toString(cs.getModifiers());
			System.out.print("    ");
			if (modifiers.length() > 0) System.out.print(modifiers + " ");
			System.out.print(name + "(");
			
			Class[] param = cs.getParameterTypes();
			if (param.length > 0) {
				System.out.print(param[0].getName());
				for (int i = 1; i < param.length; ++i) {
					System.out.print(", " + param[i].getName());
				}
			}			
			System.out.println(");");
		}
	}
	
	public static void printMethods(Class cl) {
		Method[] methods = cl.getDeclaredMethods();
		
		for (var mt : methods) {
			Class retType = mt.getReturnType();
			String name = mt.getName();
			String modifiers = Modifier.toString(mt.getModifiers());
			System.out.print("    ");
			if (modifiers.length() > 0) System.out.print(modifiers + " ");
			System.out.print(retType.getName() + " " + name + "(");
			
			Class[] param = mt.getParameterTypes();
			if (param.length > 0) {
				System.out.print(param[0].getName());
				for (int i = 1; i < param.length; ++i) {
					System.out.print(", " + param[i].getName());
				}
			}			
			System.out.println(");");
		}
	}
	
	//Expanding array by reflection
	//the type of array is Object, not Object[]
	//because the Integer array can change to Object,but can't to a Object array
	/**  
	 * @Description: 
	 * @param arr
	 * @param newLength
	 * @return Object 
	 */
	public static Object myCopyOf(Object arr, int newLength) {
		Class cl = arr.getClass();
		//check if it's an Array
		if (!cl.isArray()) return null;
		
		int oldLen = Array.getLength(arr);
		Class curType = cl.getComponentType();
		//use the method "newInstance" which can generate a new object
		Object newArr = Array.newInstance(curType, newLength);
		
		//copy oldArr to newArr
		System.arraycopy(arr, 0, newArr, 0, Math.min(oldLen, newLength));
		return newArr;
	}
	
	//exercise of breaking singleton pattern by getting the private constructor
	/*public void destroySingleMode(int x) throws ReflectiveOperationException {
		//try to modify the one of private field
		Test t1 = Test.getInstance();
		Class cl = t1.getClass();
		Field f = cl.getDeclaredField("salary");
		f.setAccessible(true);
		var value = f.get(t1);
		System.out.println("reflection value:" + value);
		System.out.println("actual value:" + t1.getSalary());
		f.set(t1, x);
		System.out.println("set value:" + x);
		//t1.salary = 100;
		System.out.println("actual value:" + t1.getSalary());
		
		//new a Object by private constructor with reflection
		Constructor<Test> cs = Test.class.getDeclaredConstructor();
		cs.setAccessible(true);
		Test t2 = cs.newInstance();
		System.out.println(t2.getId());
	}*/
}
